package org.ponly.fs.util;

import org.apache.commons.io.output.NullOutputStream;
import org.im4java.core.*;
import org.im4java.process.OutputConsumer;
import org.ponly.fs.FileSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.text.MessageFormat;
import java.util.Date;

public class StorageUtil {
    private static final Logger LOG = LoggerFactory.getLogger(StorageUtil.class);
    public static final String SIZE_SEPARATOR = "x";                // 文件尺寸分隔符
    public static final String THUMB_NAME_FMT = "{0}_{1}x{2}.{3}";  // 缩略图命名规则

    private static java.util.List<String> allowSizeList;                             // 允许的缩略图尺寸
    private static boolean storeThumbnail = true;

    /**
     * 存储
     * @param originalFilename
     * @param generateNewName
     * @param in
     * @param override
     * @return
     * @throws IOException
     */
    public static String store(FileSystem fs, String originalFilename, boolean generateNewName, InputStream in, boolean override) throws IOException {
        String filename = originalFilename;
        String suffix = null != originalFilename ? FileUtil.getExtension(originalFilename, true) : "";
        if (null == originalFilename || generateNewName) {
            filename = generateFilename() + suffix;
        }

        try {
            StorageUtil.doStore(fs,filename, in, override);
            return filename;
        } catch (IOException e) {
            LOG.warn("文件存储异常: {}", e);
            throw new IOException(e);
        } finally {
            IOUtil.close(in);
        }
    }

    public static void doStore(FileSystem fs,String fileName, InputStream in, boolean override) throws IOException {
        fs.flow(fileName,in,override);
    }

    /**
     * 生成文件名
     * @return
     */
    private static String generateFilename() {
        byte[] bytes = Bytes.toBytes(new Date().getTime() + RandomUtil.next(0, Long.MAX_VALUE));
        return HexUtil.encode(bytes);
    }


    /**
     *  图片裁剪与获取
     */

    /**
     * 读取输入流
     * @param id
     * @return
     */
    public static final InputStream readAsStream(String id, boolean tabloid,FileSystem fs) {
        return tabloid ? doImgOpen(id,fs) : doOpen(id,fs);
    }

    /**
     * 获取文件输入流
     * @param
     * @return
     */
    protected static InputStream doOpen(String fileName,FileSystem fs) {
        try{
            return fs.open(fileName);
        }catch (Exception e){
            LOG.warn("文件读取异常: {}", e);
            return  null;
        }
    }

    /**
     * 图片裁剪，如果不包含尺寸信息，则返回null
     * @param fileName
     * @param fs
     * @return
     */
    public static InputStream doImgOpen(String fileName,FileSystem fs) {
        // 可能是需要生成缩略图的请求
        int origNameLength = getOriginalNameLength(fileName);      // 原始图片名称
        if (origNameLength == fileName.length()) {                 // 不包含尺寸信息
            return null;
        }

        String origName = fileName.substring(0, origNameLength);            // 未缩放的文件名
        String suffix = FileUtil.getExtension(origName, true);             // 文件后缀

        InputStream in = doOpen(fileName + suffix,fs);
        if (null != in) {       // 如果存在该缩略图，则直接返回
            return in;
        }

        // 提取缩略尺寸名字
        String sizeStr;
        if (fileName.endsWith(suffix)) {
            sizeStr = fileName.substring(origNameLength + 1, fileName.length() - suffix.length());
        } else {
            sizeStr = fileName.substring(origNameLength + 1);
        }

        if (null != allowSizeList && allowSizeList.size() > 0 && !allowSizeList.contains(fileName)) {
            LOG.debug("缩略图尺寸不合法: {} {}", origName, fileName);
            return in;
        }

        // 获取所缩略图大小
        String[] size = sizeStr.split(SIZE_SEPARATOR);
        if (2 != size.length) {
            LOG.debug("缩略图尺寸不合法: {} {}", origName, fileName);
            return null;
        }
        boolean ratio = true; //是否保持等比
        if(size[1].endsWith("!")) {
            ratio = false;
            size[1] = size[1].substring(0,size[1].length()-1);
        }

        // 读取原始文件
        in = doOpen(origName,fs);
        if (null == in) {
            LOG.debug("源文件不存在: {}", origName);
            return null;
        }

        try {
            int width = Integer.parseInt(size[0]);
            int height = Integer.parseInt(size[1]);
            String thumbnail = getThumbnailName(origName, width, height);
            File tempFile = File.createTempFile("ponly-ils", suffix);
            IOUtil.flow(in, new FileOutputStream(tempFile), true, true);

            File thumb = new File(tempFile.getAbsolutePath() + "." + sizeStr + suffix);
            LOG.debug("生成缩略图 {}", thumbnail);
            // 生成缩略图
            generateThumb(tempFile, thumb, width, height,ratio);

            if (storeThumbnail) {  //是否保存缩略图
                StorageUtil.doStore(fs,thumbnail,new FileInputStream(thumb),true);
            }
            tempFile.deleteOnExit();
            return new FileInputStream(thumb);
        } catch (Exception e) {
            // 如果异常，返回原图
            LOG.warn("自动生成缩略图异常 {}", e);
            return null;
        }
    }

    private static int getOriginalNameLength(String thumbName) {
        int len = thumbName.length();
        char[] chars = thumbName.toCharArray();

        for (int i = 0; i < chars.length - 1; i++) {
            char cur = chars[i];
            char next = chars[i + 1];

            if ('_' == cur && Character.isDigit(next)) {
                len = i;
            }
        }
        return len;
    }


    private static String getThumbnailName(String origName, int w, int h) {
        String suffix = FileUtil.getExtension(origName, false);
        return MessageFormat.format(THUMB_NAME_FMT, origName, w, h, suffix);
    }

    /**
     * 生成缩略图
     *
     * @param source
     * @param thumb
     * @param width
     * @param height
     */
    private static void generateThumb(File source, File thumb, int width, int height, boolean ratio) {
        try {
            try {
                if (gmIsValid) {
                    IMOperation op = new IMOperation();
                    // op.resize(width, height, "^").gravity("center").extent(width, height).quality(90d);
                    op.resize(width, height, ratio ? "" : "!").gravity("center").quality(90d);
                    op.addImage().addImage();

                    runIMConvertOps(op, source.getAbsolutePath(), thumb.getAbsolutePath());
                    return;
                }
            } catch (Exception e) {
                LOG.warn("GraphicsMagic execute fail: ", e);
            }

            String format = FileUtil.getExtension(thumb.getName(), false);
            BufferedImage orignal = Images.read(source);
            Image result = Images.scale(orignal, width, height, Image.SCALE_FAST | Image.SCALE_SMOOTH);
            Images.write(result, format, thumb);
        } catch (IOException e) {
            LOG.warn("GraphicsMagic execute fail: ", e);
        }
    }


    /**
     * 运行IM4j 命令
     * @param ops
     * @param args
     * @throws InterruptedException
     * @throws IOException
     * @throws IM4JavaException
     */
    private static void runIMConvertOps(IMOps ops, Object... args) throws InterruptedException, IOException, IM4JavaException {
        ConvertCmd cmd = new ConvertCmd(true);
        cmd.run(ops, args);
    }







    private static boolean gmIsValid = false;
    private static boolean cmykSupported = false;

    static {
        try {
            // 测试 graphics magic
            ImageCommand gm = new ImageCommand("gm");
            gm.setOutputConsumer(NullOutputConsumer.INSTANCE);
            gm.run(new Operation().addRawArgs("-help"));

            gmIsValid = true;
            LOG.info("find GM (Graphics Magick)...");
        } catch (Exception ignore) { /* ignore */ }

        try {
            // 尝试 twelvemonkeys imageio (cmyk jpeg support)
            Thread.currentThread().getContextClassLoader().loadClass("com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReaderSpi");

            cmykSupported = true;
            LOG.info("find \"twelyemonkeys imageio-jpeg\" plugins ...");
        } catch (ClassNotFoundException ignore) { /* ignore */ }

        if (!gmIsValid && !cmykSupported) {
            LOG.warn("Application is not support CMYK images (JPEG),");
            LOG.warn("because can't find \"twelyemonkeys imageio-jpeg\" plugins or \"GM (Graphics Magick)\",");
            LOG.warn("you can find them here, imageio-jpeg  https://github.com/haraldk/TwelveMonkeys, GM: http://www.graphicsmagick.org");
        }
    }

    private static class NullOutputConsumer implements OutputConsumer {
        public static final NullOutputConsumer INSTANCE = new NullOutputConsumer();

        @Override
        public void consumeOutput(InputStream inputStream) throws IOException {
            IOUtil.flow(inputStream, NullOutputStream.NULL_OUTPUT_STREAM, true, true);
        }
    }
}
